package com.xudm.cdg.service.impl;

import cn.hutool.core.text.StrSplitter;
import com.xudm.cdg.config.MinioConfig;
import com.xudm.cdg.service.MinioService;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.Bucket;
import io.minio.messages.Item;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.checkerframework.checker.signature.qual.SignatureUnknown;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;

import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Optional;


@Component
@Slf4j
public class MinioServiceImpl implements MinioService {

    private final MinioClient minioClient;
    private final MinioConfig minIOConfig;

    public MinioServiceImpl(MinioClient minioClient, MinioConfig minIOConfig) {
        this.minioClient = minioClient;
        this.minIOConfig = minIOConfig;
    }

    /******************************  Operate Bucket Start  ******************************/

    @Override
    @SneakyThrows
    public void createBucket(String bucketName) {
        if (!bucketExists(bucketName)) {
            minioClient.makeBucket(MakeBucketArgs.builder().bucket(bucketName).build());
        }
    }

    @Override
    @SneakyThrows
    public boolean bucketExists(String bucketName) {
        return minioClient.bucketExists(BucketExistsArgs.builder().bucket(bucketName).build());
    }

    @Override
    @SneakyThrows
    public String getBucketPolicy(String bucketName) {
        return minioClient.getBucketPolicy(GetBucketPolicyArgs
                .builder()
                .bucket(bucketName)
                .build());
    }

    @Override
    @SneakyThrows
    public List<Bucket> getAllBuckets() {
        return minioClient.listBuckets();
    }

    @Override
    public Optional<Bucket> getBucket(String bucketName) {
        return getAllBuckets().stream().filter(b -> b.name().equals(bucketName)).findFirst();
    }

    @Override
    @SneakyThrows
    public void removeBucket(String bucketName) {
        minioClient.removeBucket(RemoveBucketArgs.builder().bucket(bucketName).build());
    }

    /******************************  Operate Bucket End  ******************************/

    /******************************  Operate Files Start  ******************************/

    @Override
    public boolean isFolderExist(String filePath) {
        boolean exist = false;
        try {
            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder().bucket(minIOConfig.getBucketName()).prefix(filePath).recursive(false).build());
            for (Result<Item> result : results) {
                Item item = result.get();
                if (item.isDir() && filePath.equals(item.objectName())) {
                    exist = true;
                }
            }
        } catch (Exception e) {
            log.error("[Minio工具类]>>>> 判断文件夹是否存在，异常：", e);
            exist = false;
        }
        return exist;
    }

    @Override
    public boolean isFolderExist(String bucketName, String filePath) {
        boolean exist = false;
        try {
            Iterable<Result<Item>> results = minioClient.listObjects(
                    ListObjectsArgs.builder().bucket(bucketName).prefix(filePath).recursive(false).build());
            for (Result<Item> result : results) {
                Item item = result.get();
                if (item.isDir() && filePath.equals(item.objectName())) {
                    exist = true;
                }
            }
        } catch (Exception e) {
            log.error("[Minio工具类]>>>> 判断文件夹是否存在，异常：", e);
            exist = false;
        }
        return exist;
    }

    @Override
    @SneakyThrows
    public ObjectWriteResponse createDir(String bucketName, String dirPath) {
        return minioClient.putObject(
                PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(dirPath)
                        .stream(new ByteArrayInputStream(new byte[]{}), 0, -1)
                        .build());
    }

    @Override
    public boolean isObjectExist(String filePath) {
        boolean exist = true;
        try {
            minioClient.statObject(StatObjectArgs.builder().bucket(minIOConfig.getBucketName()).object(filePath).build());
        } catch (Exception e) {
            if (!e.getMessage().equals("Object does not exist") && !e.getMessage().equals("The specified bucket does not exist")) {
                log.error("[Minio工具类]>>>> 判断该路径【/" + minIOConfig.getBucketName() + "/" + filePath + "】下文件是否存在, 异常：", e);
            }
            exist = false;
        }
        return exist;
    }

    @Override
    public boolean isObjectExist(String bucketName, String filePath) {
        boolean exist = true;
        try {
            minioClient.statObject(StatObjectArgs.builder().bucket(bucketName).object(filePath).build());
        } catch (Exception e) {
            if (!"Object does not exist".equals(e.getMessage()) && !"The specified bucket does not exist".equals(e.getMessage())) {
                log.error("[Minio工具类]>>>> 判断该路径【/" + minIOConfig.getBucketName() + "/" + filePath + "】下文件是否存在, 异常：", e);
            }
            exist = false;
        }
        return exist;
    }

    @Override
    @SneakyThrows
    public List<Item> getAllObjectsByPrefix(String prefix, boolean recursive) {
        List<Item> list = new ArrayList<>();
        Iterable<Result<Item>> objectsIterator = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(minIOConfig.getBucketName()).prefix(prefix).recursive(recursive).build());
        if (objectsIterator != null) {
            for (Result<Item> o : objectsIterator) {
                Item item = o.get();
                list.add(item);
            }
        }
        return list;
    }

    @Override
    @SneakyThrows
    public List<Item> getAllObjectsByPrefix(String bucketName, String prefix, boolean recursive) {
        List<Item> list = new ArrayList<>();
        Iterable<Result<Item>> objectsIterator = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).prefix(prefix).recursive(recursive).build());
        if (objectsIterator != null) {
            for (Result<Item> o : objectsIterator) {
                Item item = o.get();
                list.add(item);
            }
        }
        return list;
    }

    @Override
    @SneakyThrows
    public InputStream getObject(String filePath) {
        return minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(minIOConfig.getBucketName())
                        .object(filePath)
                        .build());
    }

    @Override
    @SneakyThrows
    public InputStream getObject(String bucketName, String filePath) {
        return minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(filePath)
                        .build());
    }

    @Override
    @SneakyThrows
    public InputStream getObject(String filePath, long offset, long length) {
        return minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(minIOConfig.getBucketName())
                        .object(filePath)
                        .offset(offset)
                        .length(length)
                        .build());
    }

    @Override
    @SneakyThrows
    public InputStream getObject(String bucketName, String filePath, long offset, long length) {
        return minioClient.getObject(
                GetObjectArgs.builder()
                        .bucket(bucketName)
                        .object(filePath)
                        .offset(offset)
                        .length(length)
                        .build());
    }

    @Override
    public Iterable<Result<Item>> listObjects(String prefix, boolean recursive) {
        return minioClient.listObjects(
                ListObjectsArgs.builder()
                        .bucket(minIOConfig.getBucketName())
                        .prefix(prefix)
                        .recursive(recursive)
                        .build());
    }

    @Override
    public Iterable<Result<Item>> listObjects(String bucketName, String prefix, boolean recursive) {
        return minioClient.listObjects(
                ListObjectsArgs.builder()
                        .bucket(bucketName)
                        .prefix(prefix)
                        .recursive(recursive)
                        .build());
    }

    @Override
    public ObjectWriteResponse uploadFile(MultipartFile file, String filePath) {
        try (InputStream inputStream = file.getInputStream()) {
            return minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(minIOConfig.getBucketName())
                            .object(filePath)
                            .contentType(file.getContentType())
                            .stream(inputStream, inputStream.available(), -1)
                            .build());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public ObjectWriteResponse uploadFile(String bucketName, MultipartFile file, String filePath) {
        try (InputStream inputStream = file.getInputStream()) {
            return minioClient.putObject(
                    PutObjectArgs.builder()
                            .bucket(bucketName)
                            .object(filePath)
                            .contentType(file.getContentType())
                            .stream(inputStream, inputStream.available(), -1)
                            .build());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    @SneakyThrows
    public ObjectWriteResponse uploadFile(String filePath, String localFilePath) {
        return minioClient.uploadObject(
                UploadObjectArgs.builder()
                        .bucket(minIOConfig.getBucketName())
                        .object(filePath)
                        .filename(localFilePath)
                        .build());
    }

    @Override
    @SneakyThrows
    public ObjectWriteResponse uploadFile(String bucketName, String filePath, String localFilePath) {
        return minioClient.uploadObject(
                UploadObjectArgs.builder()
                        .bucket(bucketName)
                        .object(filePath)
                        .filename(localFilePath)
                        .build());
    }

    @Override
    @SneakyThrows
    public ObjectWriteResponse uploadFile(String filePath, InputStream inputStream) {
        return minioClient.putObject(
                PutObjectArgs.builder()
                        .bucket(minIOConfig.getBucketName())
                        .object(filePath)
                        .stream(inputStream, inputStream.available(), -1)
                        .build());
    }

    @Override
    @SneakyThrows
    public ObjectWriteResponse uploadFile(String bucketName, String filePath, InputStream inputStream) {
        return minioClient.putObject(
                PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(filePath)
                        .stream(inputStream, inputStream.available(), -1)
                        .build());
    }

    @Override
    @SneakyThrows
    public StatObjectResponse getFileStatusInfo(String filePath) {
        return minioClient.statObject(
                StatObjectArgs.builder()
                        .bucket(minIOConfig.getBucketName())
                        .object(filePath)
                        .build());
    }

    @Override
    @SneakyThrows
    public StatObjectResponse getFileStatusInfo(String bucketName, String filePath) {
        return minioClient.statObject(
                StatObjectArgs.builder()
                        .bucket(bucketName)
                        .object(filePath)
                        .build());
    }

    @Override
    public boolean copyFile(String bucketName, String filePath, String targetBucketName, String targetFilePath, boolean isCover) {
        boolean result = true;
        try {
            if (!isObjectExist(targetBucketName, targetFilePath) || isCover) {
                minioClient.copyObject(
                        CopyObjectArgs.builder()
                                .source(CopySource.builder().bucket(bucketName).object(filePath).build())
                                .bucket(targetBucketName)
                                .object(targetFilePath)
                                .build());
            } else {
                log.error("[Minio工具类]>>>> 目标路径下有同名称文件，文件移动失败！");
                return result;
            }
        } catch (Exception e) {
            e.printStackTrace();
            result = false;
        }
        return result;
    }

    @Override
    public boolean moveFile(String bucketName, String filePath, String targetBucketName, String targetFilePath, boolean isCover) {
        boolean result = true;
        try {
            copyFile(bucketName, filePath, targetBucketName, targetFilePath, isCover);
            removeFile(bucketName, filePath);
        } catch (Exception e) {
            e.printStackTrace();
            result = false;
        }
        return result;
    }

    @Override
    public boolean renameFile(String bucketName, String filePath, String newFileName) {
        boolean result = true;
        try {
            String pathPrefix = filePath.substring(0, filePath.lastIndexOf("/") + 1);
            copyFile(bucketName, filePath, bucketName, pathPrefix + newFileName, true);
            removeFile(bucketName, filePath);
        } catch (Exception e) {
            e.printStackTrace();
            result = false;
        }
        return result;
    }

    @Override
    public boolean renameFile(String bucketName, String filePath, String newFileName, boolean isCover) {
        boolean result = true;
        try {
            String pathPrefix = filePath.substring(0, filePath.lastIndexOf("/") + 1);
            copyFile(bucketName, filePath, bucketName, pathPrefix + newFileName, isCover);
            removeFile(bucketName, filePath);
        } catch (Exception e) {
            e.printStackTrace();
            result = false;
        }
        return result;
    }

    @Override
    public boolean removeFile(String filePath) {
        boolean result = true;
        try {
            if (isObjectExist(minIOConfig.getBucketName(), filePath)) {
                minioClient.removeObject(
                        RemoveObjectArgs.builder()
                                .bucket(minIOConfig.getBucketName())
                                .object(filePath)
                                .build());
            }
        } catch (Exception e) {
            e.printStackTrace();
            result = false;
        }
        return result;
    }

    @Override
    public boolean removeFile(String bucketName, String filePath) {
        boolean result = true;
        try {
            if (isObjectExist(bucketName, filePath)) {
                minioClient.removeObject(
                        RemoveObjectArgs.builder()
                                .bucket(bucketName)
                                .object(filePath)
                                .build());
            }
        } catch (Exception e) {
            e.printStackTrace();
            result = false;
        }
        return result;
    }

    @Override
    public boolean removeFiles(List<String> filepaths) {
        List<String> objects = new LinkedList<>();
        for (String filepath : filepaths) {
            try {
                removeFile(filepath);
            } catch (Exception e) {
                log.info("删除的数据有：" + objects.toString());
                log.info("未删除的数据有：" + filepaths.toString());
                log.error("[Minio工具类]>>>> 批量删除文件，异常：", e);
                return false;
            }
            objects.add(filepath);
            filepaths.remove(filepath);
        }
        return true;
    }

    @Override
    public boolean removeFiles(String bucketName, List<String> filepaths) {
        List<String> objects = new LinkedList<>();
        for (String filepath : filepaths) {
            try {
                removeFile(bucketName, filepath);
            } catch (Exception e) {
                log.info("删除的数据有：" + objects.toString());
                log.info("未删除的数据有：" + filepaths.toString());
                log.error("[Minio工具类]>>>> 批量删除文件，异常：", e);
                return false;
            }
            objects.add(filepath);
            filepaths.remove(filepath);
        }
        return true;
    }

    @Override
    @SneakyThrows
    public String getPresignedObjectUrl(String bucketName, String filePath) {
        GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder()
                .bucket(bucketName)
                .object(filePath)
                .method(Method.GET).build();
        return minioClient.getPresignedObjectUrl(args);
    }

    @Override
    @SneakyThrows
    public String getPresignedObjectUrl(String bucketName, String filePath, Integer expires) {
        GetPresignedObjectUrlArgs args = GetPresignedObjectUrlArgs.builder().
                expiry(expires)
                .bucket(bucketName)
                .object(filePath).build();
        return minioClient.getPresignedObjectUrl(args);
    }

    /******************************  Operate Files End  ******************************/

    /******************************  Operate Other Start  ******************************/

    @Override
    public String getBucketNameByURL(String url) {
        List<String> urlParams = StrSplitter.split(url, '/', 0, true, true);
        for (int i = 0; i < urlParams.size(); i++) {
            if (isObjectExist(String.join("/", urlParams.subList(i, urlParams.size())))) {
                return urlParams.get(i - 1);
            }
            if (i > 1) {
                if (urlParams.get(i - 1).indexOf(".") > -1) {
                    continue;
                }
                if (isObjectExist(urlParams.get(i - 1), String.join("/", urlParams.subList(i, urlParams.size())))) {
                    return urlParams.get(i - 1);
                }
            }
        }
        return "";
    }

    @Override
    public String getFileNameByURL(String url) {
        return url.substring(url.lastIndexOf("/") + 1);
    }

    @Override
    public String getFilePathByURL(String url) {
        List<String> urlParams = StrSplitter.split(url, '/', 0, true, true);
        for (int i = 0; i < urlParams.size(); i++) {
            if (isObjectExist(String.join("/", urlParams.subList(i, urlParams.size())))) {
                return String.join("/", urlParams.subList(i, urlParams.size()));
            }
            if (i > 1) {
                if (urlParams.get(i - 1).indexOf(".") > -1) {
                    continue;
                }
                if (isObjectExist(urlParams.get(i - 1), String.join("/", urlParams.subList(i, urlParams.size())))) {
                    return String.join("/", urlParams.subList(i, urlParams.size()));
                }
            }
        }
        return "";
    }

    @Override
    public String getFullFileNameByURL(String url) {
        return url.substring(url.lastIndexOf("/") + 1);
    }

    @Override
    public String getUploadPath(String filePath) {
        filePath.replaceAll("\\\\", "/");
        String separator = "";
        if (!filePath.substring(0, 1).equals("/")) {
            separator = "/";
        }
        return "/" + minIOConfig.getBucketName() + separator + filePath;
    }

    @Override
    public String getUploadPath(String bucketName, String filePath) {
        filePath.replaceAll("\\\\", "/");
        String separator = "";
        if (!filePath.substring(0, 1).equals("/")) {
            separator = "/";
        }
        return "/" + bucketName + separator + filePath;
    }

    /******************************  Operate Other End  ******************************/

}
